package roborally.model;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class for dealing with batteries involving an amount of energy and a weight.
 * 
 * @invar	The amount of energy of each battery must be a legal amount of energy for a battery.
 *          | canHaveAsEnergy(getEnergy())       
 * 
 * @version	1.0
 * @author  Pieter Robberechts	(1e Bach Informatica)
 * @author	Jeroen Sanders		(1e Bach Informatica)
 *
 */
public class Battery extends Item implements IEnergyHolder {

	/**
	 * Initialize this new battery with given amount of energy and given weight.
	 * 
	 * @param	energyAmount
	 * 			The amount of energy for this new battery.
	 * @param	weight
	 * 			The weight for this new battery (in grams).
	 * @pre		...
	 * 			| canHaveAsEnergy(energyAmount)
	 * @effect	...
	 * 			| super(weight)
	 * @effect	...
	 * 			| setEnergy(energyAmount)
	 */
	public Battery(EnergyAmount energyAmount, Weight weight){
		super(weight);
		assert(canHaveAsEnergy(energyAmount));
		setEnergy(energyAmount);
	}
	
	/**
	 * Return a standard weight for a battery (in grams).
	 */
	@Override @Basic @Raw
	public Weight getStandardWeight() {
		return new Weight(100);
	}
	
	/**
	 * Check whether the given amount of energy is a valid amount of energy for a battery.
	 * 
	 * @param 	ws
	 * 			The amount of energy to check.
	 * @return	...
	 * 			| result == ! ws.moreThan(getMaxEnergy())
	 */
	@Raw
	public boolean canHaveAsEnergy(EnergyAmount ws){
		return (! ws.moreThan(getMaxEnergy()));
	}
	
	/**
	 * Set the amount of energy of this battery to the given amount of energy.
	 * 
	 * @param 	energyAmount
	 * 			The amount of energy for this battery.
	 * @pre		...
	 * 			| canHaveAsEnergy(energyAmount)
     * @post   	...
     *       	| new.getEnergy().isIdenticalTo(energyAmount)
	 */
	@Model @Raw
	private void setEnergy(EnergyAmount energyAmount){
		assert canHaveAsEnergy(energyAmount);
		this.energyAmount = energyAmount;
	}
	
	/**
	 * Return the amount of energy of this robot.
	 * 
	 */
	@Raw @Override
	public EnergyAmount getEnergy(){
		return energyAmount;
	}
	
	/**
	 * Return the maximal amount of energy any battery can store.
	 * 
	 * @return	...
	 * 			| result == new EnergyAmount(5000)
	 */
	@Immutable
	private EnergyAmount getMaxEnergy(){
		return new EnergyAmount(5000);
	}
	
	/**
	 * Variable registering the amount of energy stored in this battery.
	 */
	private EnergyAmount energyAmount;
	
	/**
	 * Transfer energy from this battery to the given robot.
	 * 	
	 * @param	robot
	 * 			The robot which should be recharged with the energy of this battery.
	 * @pre		...
	 * 			| robot != null
	 * @pre		...
	 * 			| ! this.isTerminated() && ! robot.isTerminated()
	 * @effect	...
	 * 			| if( !(getEnergy().moreThan( robot.getMaxEnergy().subtract(robot.getEnergy()) )) )
	 *			|	then transferEnergy(robot, getEnergy()) && robot.drop(this) && terminate()
	 *			| if(getEnergy().moreThan( robot.getMaxEnergy().subtract(robot.getEnergy()) ))
	 *			|	then transferEnergy( robot, robot.getMaxEnergy().subtract( robot.getEnergy() ) )
	 */
	@Override
	public void use(Robot robot){
		assert(robot != null);
		assert(! this.isTerminated() && ! robot.isTerminated());
		EnergyAmount availableEnergy = getEnergy();
		EnergyAmount maxEnergyForRobot = robot.getMaxEnergy().subtract(robot.getEnergy());
		if(! availableEnergy.moreThan(maxEnergyForRobot) ){
			transferEnergy(robot, availableEnergy);
			robot.drop(this);
			terminate();
		}else {
			transferEnergy(robot, maxEnergyForRobot);
		}
	}
	
	/**
	 * Recharges a battery with a given amount of energy.
	 * 
	 * @param 	energyAmount
	 * 		  	The amount of energy to add.
	 * @pre		...
	 * 			| energyAmount != null
	 * @pre 	...
	 * 			| canHaveAsEnergy( getEnergy().add(energyAmount) )
	 * @effect	...
	 * 			| setEnergy( getEnergy().add(energyAmount) )
	 */
	@Override
	public void recharge(EnergyAmount amount){
		assert (energyAmount != null);
		assert canHaveAsEnergy(getEnergy().add(energyAmount));
		setEnergy(getEnergy().add(energyAmount));
	}
	
	/**
	 * Transfer energy from a battery to another energy holder.
	 * 
	 * @param	other
	 * 			The energy holder to transfer an amount of energy to.
	 * @param	amount
	 * 			The amount of energy to transfer.
	 * @pre		...
	 * 			| other != null
	 * @pre		...
	 * 			| !other.isTerminated()
	 * @pre		...
	 * 			| amount != null
	 * @pre		...
	 * 			| other.canHaveAsEnergy(getEnergy().add(amount))
	 * @pre		...
	 * 			| this.canHaveAsEnergy(getEnergy().subtract(amount))
	 * @effect	...
	 * 			| setEnergy(getEnergy().subtract(amount))
	 * @effect	...
	 * 			| other.recharge(amount)
	 */
	@Override
	public void transferEnergy(IEnergyHolder other, EnergyAmount amount){
		assert (other != null);
		assert !other.isTerminated();
		assert (amount != null);
		assert other.canHaveAsEnergy(getEnergy().add(amount));
		assert this.canHaveAsEnergy(getEnergy().subtract(amount));
		setEnergy(getEnergy().subtract(amount));
		other.recharge(amount);
	}
	
	/**
	 * Return a textual representation of a battery.
	 * 
	 * @return	...
	 * 			| if getPosition() != null 
	 *			|	then result == "Battery [Energy: " + getEnergy() + ", Weight: " + getWeight() + ", Position: " + getPosition().toString() + "]";
	 *			| else
	 *			|	then result == "Battery [Energy: " + getEnergy() + ", Weight " + getWeight() + " ]";
	 * @return	...
	 * 			| if isTerminated()
	 *			|	then result == "This battery is terminated."
	 */
	@Override
	public String toString(){
		if(isTerminated())
			return "This battery is terminated.";
		if(getPosition() != null)
			return "Battery [Energy: " + getEnergy() + ", Weight: " + getWeight() + ", Position: " + getPosition().toString() + "]";
		else
			return "Battery [Energy: " + getEnergy() + ", Weight " + getWeight() + " ]";

	}

}
